function createPatchFunction() {
    
    /**
     * 插入节点
     * 1、无兄弟节点，直接插入父节点内
     * 2、有兄弟，插在兄弟前面
     */
    function insert(parent, elm, ref) {

        if (parent) {
            if (ref) {
                if (ref.parentNode === parent) {
                    parent.insertBefore(elm, ref);
                }
            } else {
                parent.appendChild(elm);
            }
        }
    }

    function createElm(
        vnode,parentElm,refElm
    ) {

        var children = vnode.children;
        var tag = vnode.tag;

        if (tag) {
            
            vnode.elm = document.createElement(tag) 

            // 先把 子节点插入 vnode.elm，然后再把 vnode.elm 插入parent
            createChildren(vnode, children);

            //  插入DOM 节点
            insert(parentElm, vnode.elm, refElm);
        } 
        else {
            vnode.elm = document.createTextNode(vnode.text) 
            insert(parentElm, vnode.elm, refElm);
        }
    }

    //  批量调用 createElm 新建节点
    function addVnodes(
        parentElm, refElm, vnodes, 
        startIdx, endIdx
    ) {
        for (; startIdx <= endIdx; ++startIdx) {
            createElm(
                vnodes[startIdx], parentElm, refElm
            );
        }
    }

    function removeNode(el) {
        var parent = el.parentNode 

        // element may have already been removed due to v-html / v-text
        if (parent) {
            parent.removeChild(el);
        }
    }

    // 会移除 头和 尾 
    function removeVnodes(vnodes, startIdx, endIdx) {

        for (; startIdx <= endIdx; ++startIdx) {

            var ch = vnodes[startIdx];
            if (ch) removeNode(ch.elm);
        }
    }

    /***
     * 创建子节点，如果子节点是数组，则遍历执行 createElm 方法
     * 如果子节点的 text 属性有数据，则在真实 DOM 中插入文本内容
     */
    function createChildren(vnode, children) {

        if (Array.isArray(children)) {

            for (var i = 0; i < children.length; ++i) {
                createElm(children[i], vnode.elm, null);
            }
        } 
        else if (
            typeof value === 'string' ||
            typeof value === 'number' ||
            typeof value === 'boolean'
        ) {
            vnode.elm.appendChild(
                document.createTextNode(String(vnode.text))
            )
        }
    }
    
    // 把 key 作为 键名，保存 index
    // 产生 key 与 index 索引对应的一个 map 表
    function createKeyToOldIdx(children, beginIdx, endIdx) {

        var i, key;
        var map = {};

        for (i = beginIdx; i <= endIdx; ++i) {
            key = children[i].key;
            if (key) { map[key] = i; }
        }
        return map
    }

    // 找到一个节点在 旧节点中的位置
    function findIdxInOld(node, oldCh, start, end) {

        for (var i = start; i < end; i++) {
            var c = oldCh[i];
            if (c && sameVnode(node, c)) { return i }
        }
    }

    function sameVnode(a, b) {
        return (
            a.key === b.key && 
            a.tag === b.tag &&
            a.data === b.data &&
            sameInputType(a, b)
        )
    }
    
    function sameInputType(a, b) {
        if (a.tag !== 'input')  return true 
        var i;
        var types = ['text,number,password,search,email,tel,url']
        var typeA = (i = a.data) && (i = i.attrs) && i.type;
        var typeB = (i = b.data) && (i = i.attrs) && i.type;
        return typeA === typeB || types.indexOf(typeA)>-1 && types.indexOf(typeB)>-1
    }

    // 一个 while 循环 比较更新，一个 for 遍历添加
    function updateChildren(parentElm, oldCh, newCh) {

        var oldStartIdx = 0;
        var newStartIdx = 0;

        var oldEndIdx = oldCh.length - 1;
        var oldStartVnode = oldCh[0];
        var oldEndVnode = oldCh[oldEndIdx];

        var newEndIdx = newCh.length - 1;
        var newStartVnode = newCh[0];
        var newEndVnode = newCh[newEndIdx];

        var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

        // old 和 new 先 头比头，尾比尾，头比尾，尾比头，实在不行，遍历找相同Vnode
        // 不断地更新 OldIndex 和 OldVnode ，newIndex 和 newVnode
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

            // 先把 oldStartVnode 和 oldEndVnode 变成 有值

            // 只有  oldStartIdx  变化 , Vnode has been moved left
            if (!oldStartVnode) {
                oldStartVnode = oldCh[++oldStartIdx]; 
            } 
            
            // 只有  oldEndIdx  变化
            else if (!oldEndVnode) {
                oldEndVnode = oldCh[--oldEndIdx];
            } 
            
            //  newStartIdx 、 oldStartIdx  变化
            else if (sameVnode(oldStartVnode, newStartVnode)) {
                
                patchVnode(oldStartVnode, newStartVnode);

                oldStartVnode = oldCh[++oldStartIdx];
                newStartVnode = newCh[++newStartIdx];
            } 
            
            //  newEndIdx 、 oldEndIdx  变化
            else if (sameVnode(oldEndVnode, newEndVnode)) {

                patchVnode(oldEndVnode, newEndVnode);

                oldEndVnode = oldCh[--oldEndIdx];
                newEndVnode = newCh[--newEndIdx];
            } 
            
            /**
             * 头比头，尾比尾  和  头比尾，尾比头的区别是，
             * 头比尾，尾比头 的时候，会进行 dom 插入操作，而 头头，尾尾单纯只是比较更新，不会移动dom
             * newEndIdx  、 oldStartIdx  变化
             * Vnode moved right
             */
            else if (sameVnode(oldStartVnode, newEndVnode)) { 

                patchVnode(oldStartVnode, newEndVnode);

                // oldStartVnode 放到 oldEndVnode 后面，还要找到 oldEndValue 后面的节点
                parentElm.insertBefore(
                    oldStartVnode.elm, 
                    oldEndVnode.elm.nextSibling
                );

                oldStartVnode = oldCh[++oldStartIdx];
                newEndVnode = newCh[--newEndIdx];
            } 
            
            //  newStartIdx 、 oldEndIdx  变化 ，Vnode moved left
            else if (sameVnode(oldEndVnode, newStartVnode)) { 

                patchVnode(oldEndVnode, newStartVnode);

                // oldEndVnode 放到 oldStartVnode 前面
                parentElm.insertBefore(
                    oldEndVnode.elm, 
                    oldStartVnode.elm.nextSibling
                );

                oldEndVnode = oldCh[--oldEndIdx];
                newStartVnode = newCh[++newStartIdx];
            }
            
            // 只有  newStartIdx  变化
            else {

                // oldKeyToIdx 是一个 把 Vnode 的 key 和 index 转换的 map
                if (!oldKeyToIdx) { 
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); 
                }

                // 使用 newStartVnode 去 OldMap 中寻找 相同节点
                idxInOld = 
                    newStartVnode.key
                    ? oldKeyToIdx[newStartVnode.key] 

                    // 孩子一个个比对，如果 newStartVnode 和 旧孩子某个一样，就返回 该旧孩子的 index
                    : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
                
                //  新孩子中，存在一个新节点，老节点中没有
                if (!idxInOld) { // New element
                    
                    // createElm 什么作用，需要查看
                    //  把  newStartVnode 插入 oldStartVnode 的前面
                    createElm(newStartVnode, parentElm, oldStartVnode.elm);
                } 
                
                else {
                    
                    //  找到 oldCh 中 和 newStartVnode 一样的节点
                    vnodeToMove = oldCh[idxInOld];

                    if (sameVnode(vnodeToMove, newStartVnode)) {

                        patchVnode(vnodeToMove, newStartVnode);
                        
                        // 删除这个 index
                        oldCh[idxInOld] = undefined;

                        // 把 vnodeToMove 移动到  oldStartVnode 前面
                        parentElm.insertBefore(
                            vnodeToMove.elm, 
                            oldStartVnode.elm.nextSibling
                        );
                    } 
                    
                    // 只能创建一个新节点插入到 parentElm 的子节点中
                    else {

                        // same key but different element. treat as new element
                        createElm(
                            newStartVnode, parentElm, oldStartVnode.elm
                        );
                    }
                }

                // 更新 newStartIdx
                newStartVnode = newCh[++newStartIdx];
            }
        }

        /***
         * 很多种情况
         * 1、oldCh 存在，但是 节点全部一样，那么 oldStartIdx 和 oldEndIdx  没有变化，
         *    newCh 逐个 跟 oldCh 比较，因为不一一样，所有逐个添加进了parent
         */
        // 说明老节点比对完了，但是新节点还有多的，需要把新节点
        if (oldStartIdx > oldEndIdx) {

            // ？：newCh 中的 elm 绑定过了dom 了吗
            refElm = !newCh[newEndIdx + 1] ? null : newCh[newEndIdx + 1].elm;

            addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx);
        } 
        
        // 说明新节点比对完了，老节点还有多，需要删除多余的 老节点
        // 一种情况是 oldCh 和 newCh 全不一样，newStartIdx 一直变化，一直把 newStartNode 添加进去，然后需要移除 oldCh
        else if (newStartIdx > newEndIdx) {
            removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
        }
    }

    // 作用感觉是 比较两个 Vnode 的 children
    function patchVnode(oldVnode, vnode) {

        if (oldVnode === vnode) return

        var elm = vnode.elm = oldVnode.elm;

        var oldCh = oldVnode.children;
        var ch = vnode.children;

        // 更新children
        if (!vnode.text) {

            // 存在 oldCh 和 ch 时
            if (oldCh && ch) {

                if (oldCh !== ch) { 
                    updateChildren(elm, oldCh, ch); 
                }
            } 
            
            // 存在 newCh 时，oldCh 只能是不存在，如果存在，就跳到上面的条件了
            else if (ch){

                if (oldVnode.text) { elm.textContent = '';  }
                
                addVnodes(elm, null, ch, 0, ch.length - 1);
            } 
            else if (oldCh) {
                removeVnodes(elm, oldCh, 0, oldCh.length - 1);
            } 
            else if (oldVnode.text) {
                elm.textContent = ''; 
            }
        } 
        else if (oldVnode.text !== vnode.text) {
            elm.textContent = vnode.text; 
        }
    }

    return function patch(
        oldVnode, vnode, 
        parentElm, refElm
    ) {

        console.log(1111,oldVnode,vnode)

        // 没有旧节点，直接生成新f节点
        // oldVnode 有可能是原生 dom，当 不是组件时，oldVnode 不是 undefined
        if (!oldVnode) {
            createElm(vnode, parentElm, refElm);
        } 
        else {

            // 是一样 Vnode
            if (sameVnode(oldVnode, vnode)) {

                // patch existing root node
                patchVnode(oldVnode, vnode);
            } 

            // 已经是不同的 vnode 了，
            else {

                // replacing existing element
                var oldElm = oldVnode.elm;
                var parentElm$1 = oldElm.parentNode 

                // create new node
                createElm(
                    vnode, parentElm$1,oldElm.nextSibling
                );

                // destroy old node
                if (parentElm$1) {
                    removeVnodes(parentElm$1, [oldVnode], 0, 0);
                } 
            }
        }
        return vnode.elm
    }
}